/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.configuration; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Stack; import org.apache.commons.collections.iterators.SingletonIterator; import org.apache.commons.collections.set.ListOrderedSet; import org.apache.commons.configuration.event.ConfigurationEvent; import org.apache.commons.configuration.event.ConfigurationListener; import org.apache.commons.configuration.tree.ConfigurationNode; import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter; import org.apache.commons.configuration.tree.DefaultConfigurationNode; import org.apache.commons.configuration.tree.DefaultExpressionEngine; import org.apache.commons.configuration.tree.ExpressionEngine; import org.apache.commons.configuration.tree.NodeAddData; import org.apache.commons.lang.StringUtils; /** * <p>A specialized configuration class that extends its base class by the * ability of keeping more structure in the stored properties.</p><p>There * are some sources of configuration data that cannot be stored very well in a * <code>BaseConfiguration</code> object because then their structure is lost. * This is especially true for XML documents. This class can deal with such * structured configuration sources by storing the properties in a tree-like * organization.</p><p>The internal used storage form allows for a more * sophisticated access to single properties. As an example consider the * following XML document:</p><p> * * <pre> * <database> * <tables> * <table> * <name>users</name> * <fields> * <field> * <name>lid</name> * <type>long</name> * </field> * <field> * <name>usrName</name> * <type>java.lang.String</type> * </field> * ... * </fields> * </table> * <table> * <name>documents</name> * <fields> * <field> * <name>docid</name> * <type>long</type> * </field> * ... * </fields> * </table> * ... * </tables> * </database> * </pre> * * </p><p>If this document is parsed and stored in a * <code>HierarchicalConfiguration</code> object (which can be done by one of * the sub classes), there are enhanced possibilities of accessing properties. * The keys for querying information can contain indices that select a certain * element if there are multiple hits.</p><p>For instance the key * <code>tables.table(0).name</code> can be used to find out the name of the * first table. In opposite <code>tables.table.name</code> would return a * collection with the names of all available tables. Similarly the key * <code>tables.table(1).fields.field.name</code> returns a collection with * the names of all fields of the second table. If another index is added after * the <code>field</code> element, a single field can be accessed: * <code>tables.table(1).fields.field(0).name</code>.</p><p>There is a * <code>getMaxIndex()</code> method that returns the maximum allowed index * that can be added to a given property key. This method can be used to iterate * over all values defined for a certain property.</p> * <p>Since the 1.3 release of <em>Commons Configuration</em> hierarchical * configurations support an <em>expression engine</em>. This expression engine * is responsible for evaluating the passed in configuration keys and map them * to the stored properties. The examples above are valid for the default * expression engine, which is used when a new <code>HierarchicalConfiguration</code> * instance is created. With the <code>setExpressionEngine()</code> method a * different expression engine can be set. For instance with * <code>{@link org.apache.commons.configuration.tree.xpath.XPathExpressionEngine}</code> * there is an expression engine available that supports configuration keys in * XPATH syntax.</p> * <p>In addition to the events common for all configuration classes hierarchical * configurations support some more events that correspond to some specific * methods and features: * <dl><dt><em>EVENT_ADD_NODES</em></dt><dd>The <code>addNodes()</code> method * was called; the event object contains the key, to which the nodes were added, * and a collection with the new nodes as value.</dd> * <dt><em>EVENT_CLEAR_TREE</em></dt><dd>The <code>clearTree()</code> method was * called; the event object stores the key of the removed sub tree.</dd> * <dt><em>EVENT_SUBNODE_CHANGED</em></dt><dd>A <code>SubnodeConfiguration</code> * that was created from this configuration has been changed. The value property * of the event object contains the original event object as it was sent by the * subnode configuration.</dd></dl></p> * <p><em>Note:</em>Configuration objects of this type can be read concurrently * by multiple threads. However if one of these threads modifies the object, * synchronization has to be performed manually.</p> * * @author Oliver Heger * @version $Id: HierarchicalConfiguration.java 722238 2008-12-01 21:28:31Z oheger $ */ public class HierarchicalConfiguration extends AbstractConfiguration implements Serializable, Cloneable { /** * Constant for the clear tree event. * @since 1.3 */ public static final int EVENT_CLEAR_TREE = 10; /** * Constant for the add nodes event. * @since 1.3 */ public static final int EVENT_ADD_NODES = 11; /** * Constant for the subnode configuration modified event. * @since 1.5 */ public static final int EVENT_SUBNODE_CHANGED = 12; /** * The serial version UID. */ private static final long serialVersionUID = 3373812230395363192L; /** Stores the default expression engine to be used for new objects.*/ private static ExpressionEngine defaultExpressionEngine; /** Stores the root node of this configuration. This field is required for * backwards compatibility only. */ private Node root; /** Stores the root configuration node.*/ private ConfigurationNode rootNode; /** Stores the expression engine for this instance.*/ private transient ExpressionEngine expressionEngine; /** * Creates a new instance of <code>HierarchicalConfiguration</code>. */ public HierarchicalConfiguration() { setRootNode(new Node()); } /** * Creates a new instance of <code>HierarchicalConfiguration</code> and * copies all data contained in the specified configuration into the new * one. * * @param c the configuration that is to be copied (if <b>null</b>, this * constructor will behave like the standard constructor) * @since 1.4 */ public HierarchicalConfiguration(HierarchicalConfiguration c) { this(); if (c != null) { CloneVisitor visitor = new CloneVisitor(); c.getRootNode().visit(visitor); setRootNode(visitor.getClone()); } } /** * Returns the root node of this hierarchical configuration. This method * exists for backwards compatibility only. New code should use the * <code>{@link #getRootNode()}</code> method instead, which operates on * the preferred data type <code>ConfigurationNode</code>. * * @return the root node */ public Node getRoot() { if (root == null && rootNode != null) { // Dynamically create a snapshot of the root node return new Node(rootNode); } return root; } /** * Sets the root node of this hierarchical configuration. This method * exists for backwards compatibility only. New code should use the * <code>{@link #setRootNode(ConfigurationNode)}</code> method instead, * which operates on the preferred data type <code>ConfigurationNode</code>. * * @param node the root node */ public void setRoot(Node node) { if (node == null) { throw new IllegalArgumentException("Root node must not be null!"); } root = node; rootNode = null; } /** * Returns the root node of this hierarchical configuration. * * @return the root node * @since 1.3 */ public ConfigurationNode getRootNode() { return (rootNode != null) ? rootNode : root; } /** * Sets the root node of this hierarchical configuration. * * @param rootNode the root node * @since 1.3 */ public void setRootNode(ConfigurationNode rootNode) { if (rootNode == null) { throw new IllegalArgumentException("Root node must not be null!"); } this.rootNode = rootNode; // For backward compatibility also set the old root field. root = (rootNode instanceof Node) ? (Node) rootNode : null; } /** * Returns the default expression engine. * * @return the default expression engine * @since 1.3 */ public static synchronized ExpressionEngine getDefaultExpressionEngine() { if (defaultExpressionEngine == null) { defaultExpressionEngine = new DefaultExpressionEngine(); } return defaultExpressionEngine; } /** * Sets the default expression engine. This expression engine will be used * if no specific engine was set for an instance. It is shared between all * hierarchical configuration instances. So modifying its properties will * impact all instances, for which no specific engine is set. * * @param engine the new default expression engine * @since 1.3 */ public static synchronized void setDefaultExpressionEngine(ExpressionEngine engine) { if (engine == null) { throw new IllegalArgumentException( "Default expression engine must not be null!"); } defaultExpressionEngine = engine; } /** * Returns the expression engine used by this configuration. This method * will never return <b>null</b>; if no specific expression engine was set, * the default expression engine will be returned. * * @return the current expression engine * @since 1.3 */ public ExpressionEngine getExpressionEngine() { return (expressionEngine != null) ? expressionEngine : getDefaultExpressionEngine(); } /** * Sets the expression engine to be used by this configuration. All property * keys this configuration has to deal with will be interpreted by this * engine. * * @param expressionEngine the new expression engine; can be <b>null</b>, * then the default expression engine will be used * @since 1.3 */ public void setExpressionEngine(ExpressionEngine expressionEngine) { this.expressionEngine = expressionEngine; } /** * Fetches the specified property. This task is delegated to the associated * expression engine. * * @param key the key to be looked up * @return the found value */ public Object getProperty(String key) { List nodes = fetchNodeList(key); if (nodes.size() == 0) { return null; } else { List list = new ArrayList(); for (Iterator it = nodes.iterator(); it.hasNext();) { ConfigurationNode node = (ConfigurationNode) it.next(); if (node.getValue() != null) { list.add(node.getValue()); } } if (list.size() < 1) { return null; } else { return (list.size() == 1) ? list.get(0) : list; } } } /** * Adds the property with the specified key. This task will be delegated to * the associated <code>ExpressionEngine</code>, so the passed in key * must match the requirements of this implementation. * * @param key the key of the new property * @param obj the value of the new property */ protected void addPropertyDirect(String key, Object obj) { NodeAddData data = getExpressionEngine().prepareAdd(getRootNode(), key); ConfigurationNode node = processNodeAddData(data); node.setValue(obj); } /** * Adds a collection of nodes at the specified position of the configuration * tree. This method works similar to <code>addProperty()</code>, but * instead of a single property a whole collection of nodes can be added - * and thus complete configuration sub trees. E.g. with this method it is * possible to add parts of another <code>HierarchicalConfiguration</code> * object to this object. (However be aware that a * <code>ConfigurationNode</code> object can only belong to a single * configuration. So if nodes from one configuration are directly added to * another one using this method, the structure of the source configuration * will be broken. In this case you should clone the nodes to be added * before calling <code>addNodes()</code>.) If the passed in key refers to * an existing and unique node, the new nodes are added to this node. * Otherwise a new node will be created at the specified position in the * hierarchy. * * @param key the key where the nodes are to be added; can be <b>null </b>, * then they are added to the root node * @param nodes a collection with the <code>Node</code> objects to be * added */ public void addNodes(String key, Collection nodes) { if (nodes == null || nodes.isEmpty()) { return; } fireEvent(EVENT_ADD_NODES, key, nodes, true); ConfigurationNode parent; List target = fetchNodeList(key); if (target.size() == 1) { // existing unique key parent = (ConfigurationNode) target.get(0); } else { // otherwise perform an add operation parent = processNodeAddData(getExpressionEngine().prepareAdd( getRootNode(), key)); } if (parent.isAttribute()) { throw new IllegalArgumentException( "Cannot add nodes to an attribute node!"); } for (Iterator it = nodes.iterator(); it.hasNext();) { ConfigurationNode child = (ConfigurationNode) it.next(); if (child.isAttribute()) { parent.addAttribute(child); } else { parent.addChild(child); } clearReferences(child); } fireEvent(EVENT_ADD_NODES, key, nodes, false); } /** * Checks if this configuration is empty. Empty means that there are no keys * with any values, though there can be some (empty) nodes. * * @return a flag if this configuration is empty */ public boolean isEmpty() { return !nodeDefined(getRootNode()); } /** * Creates a new <code>Configuration</code> object containing all keys * that start with the specified prefix. This implementation will return a * <code>HierarchicalConfiguration</code> object so that the structure of * the keys will be saved. The nodes selected by the prefix (it is possible * that multiple nodes are selected) are mapped to the root node of the * returned configuration, i.e. their children and attributes will become * children and attributes of the new root node. However a value of the root * node is only set if exactly one of the selected nodes contain a value (if * multiple nodes have a value, there is simply no way to decide how these * values are merged together). Note that the returned * <code>Configuration</code> object is not connected to its source * configuration: updates on the source configuration are not reflected in * the subset and vice versa. * * @param prefix the prefix of the keys for the subset * @return a new configuration object representing the selected subset */ public Configuration subset(String prefix) { Collection nodes = fetchNodeList(prefix); if (nodes.isEmpty()) { return new HierarchicalConfiguration(); } final HierarchicalConfiguration parent = this; HierarchicalConfiguration result = new HierarchicalConfiguration() { // Override interpolate to always interpolate on the parent protected Object interpolate(Object value) { return parent.interpolate(value); } }; CloneVisitor visitor = new CloneVisitor(); // Initialize the new root node Object value = null; int valueCount = 0; for (Iterator it = nodes.iterator(); it.hasNext();) { ConfigurationNode nd = (ConfigurationNode) it.next(); if (nd.getValue() != null) { value = nd.getValue(); valueCount++; } nd.visit(visitor); for (Iterator it2 = visitor.getClone().getChildren().iterator(); it2 .hasNext();) { result.getRootNode().addChild((ConfigurationNode) it2.next()); } for (Iterator it2 = visitor.getClone().getAttributes().iterator(); it2 .hasNext();) { result.getRootNode().addAttribute( (ConfigurationNode) it2.next()); } } // Determine the value of the new root if (valueCount == 1) { result.getRootNode().setValue(value); } return (result.isEmpty()) ? new HierarchicalConfiguration() : result; } /** * <p> * Returns a hierarchical subnode configuration object that wraps the * configuration node specified by the given key. This method provides an * easy means of accessing sub trees of a hierarchical configuration. In the * returned configuration the sub tree can directly be accessed, it becomes * the root node of this configuration. Because of this the passed in key * must select exactly one configuration node; otherwise an * <code>IllegalArgumentException</code> will be thrown. * </p> * <p> * The difference between this method and the * <code>{@link #subset(String)}</code> method is that * <code>subset()</code> supports arbitrary subsets of configuration nodes * while <code>configurationAt()</code> only returns a single sub tree. * Please refer to the documentation of the * <code>SubnodeConfiguration</code> class to obtain further information * about subnode configurations and when they should be used. * </p> * <p> * With the <code>supportUpdate</code> flag the behavior of the returned * <code>SubnodeConfiguration</code> regarding updates of its parent * configuration can be determined. A subnode configuration operates on the * same nodes as its parent, so changes at one configuration are normally * directly visible for the other configuration. There are however changes * of the parent configuration, which are not recognized by the subnode * configuration per default. An example for this is a reload operation (for * file-based configurations): Here the complete node set of the parent * configuration is replaced, but the subnode configuration still references * the old nodes. If such changes should be detected by the subnode * configuration, the <code>supportUpdates</code> flag must be set to * <b>true</b>. This causes the subnode configuration to reevaluate the key * used for its creation each time it is accessed. This guarantees that the * subnode configuration always stays in sync with its key, even if the * parent configuration's data significantly changes. If such a change * makes the key invalid - because it now no longer points to exactly one * node -, the subnode configuration is not reconstructed, but keeps its * old data. It is then quasi detached from its parent. * </p> * * @param key the key that selects the sub tree * @param supportUpdates a flag whether the returned subnode configuration * should be able to handle updates of its parent * @return a hierarchical configuration that contains this sub tree * @see SubnodeConfiguration * @since 1.5 */ public SubnodeConfiguration configurationAt(String key, boolean supportUpdates) { List nodes = fetchNodeList(key); if (nodes.size() != 1) { throw new IllegalArgumentException( "Passed in key must select exactly one node: " + key); } return supportUpdates ? createSubnodeConfiguration( (ConfigurationNode) nodes.get(0), key) : createSubnodeConfiguration((ConfigurationNode) nodes.get(0)); } /** * Returns a hierarchical subnode configuration for the node specified by * the given key. This is a short form for <code>configurationAt(key, * <b>false</b>)</code>. * * @param key the key that selects the sub tree * @return a hierarchical configuration that contains this sub tree * @see SubnodeConfiguration * @since 1.3 */ public SubnodeConfiguration configurationAt(String key) { return configurationAt(key, false); } /** * Returns a list of sub configurations for all configuration nodes selected * by the given key. This method will evaluate the passed in key (using the * current <code>ExpressionEngine</code>) and then create a subnode * configuration for each returned node (like * <code>{@link #configurationAt(String)}</code>}). This is especially * useful when dealing with list-like structures. As an example consider the * configuration that contains data about database tables and their fields. * If you need access to all fields of a certain table, you can simply do * * <pre> * List fields = config.configurationsAt("tables.table(0).fields.field"); * for(Iterator it = fields.iterator(); it.hasNext();) * { * HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next(); * // now the children and attributes of the field node can be * // directly accessed * String fieldName = sub.getString("name"); * String fieldType = sub.getString("type"); * ... * </pre> * * @param key the key for selecting the desired nodes * @return a list with hierarchical configuration objects; each * configuration represents one of the nodes selected by the passed in key * @since 1.3 */ public List configurationsAt(String key) { List nodes = fetchNodeList(key); List configs = new ArrayList(nodes.size()); for (Iterator it = nodes.iterator(); it.hasNext();) { configs.add(createSubnodeConfiguration((ConfigurationNode) it.next())); } return configs; } /** * Creates a subnode configuration for the specified node. This method is * called by <code>configurationAt()</code> and * <code>configurationsAt()</code>. * * @param node the node, for which a subnode configuration is to be created * @return the configuration for the given node * @since 1.3 */ protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node) { SubnodeConfiguration result = new SubnodeConfiguration(this, node); registerSubnodeConfiguration(result); return result; } /** * Creates a new subnode configuration for the specified node and sets its * construction key. A subnode configuration created this way will be aware * of structural changes of its parent. * * @param node the node, for which a subnode configuration is to be created * @param subnodeKey the key used to construct the configuration * @return the configuration for the given node * @since 1.5 */ protected SubnodeConfiguration createSubnodeConfiguration( ConfigurationNode node, String subnodeKey) { SubnodeConfiguration result = createSubnodeConfiguration(node); result.setSubnodeKey(subnodeKey); return result; } /** * This method is always called when a subnode configuration created from * this configuration has been modified. This implementation transforms the * received event into an event of type <code>EVENT_SUBNODE_CHANGED</code> * and notifies the registered listeners. * * @param event the event describing the change * @since 1.5 */ protected void subnodeConfigurationChanged(ConfigurationEvent event) { fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate()); } /** * Registers this instance at the given subnode configuration. This * implementation will register a change listener, so that modifications of * the subnode configuration can be tracked. * * @param config the subnode configuration * @since 1.5 */ void registerSubnodeConfiguration(SubnodeConfiguration config) { config.addConfigurationListener(new ConfigurationListener() { public void configurationChanged(ConfigurationEvent event) { subnodeConfigurationChanged(event); } }); } /** * Checks if the specified key is contained in this configuration. Note that * for this configuration the term "contained" means that the key * has an associated value. If there is a node for this key that has no * value but children (either defined or undefined), this method will still * return <b>false </b>. * * @param key the key to be chekced * @return a flag if this key is contained in this configuration */ public boolean containsKey(String key) { return getProperty(key) != null; } /** * Sets the value of the specified property. * * @param key the key of the property to set * @param value the new value of this property */ public void setProperty(String key, Object value) { fireEvent(EVENT_SET_PROPERTY, key, value, true); // Update the existing nodes for this property Iterator itNodes = fetchNodeList(key).iterator(); Iterator itValues; if (!isDelimiterParsingDisabled()) { itValues = PropertyConverter.toIterator(value, getListDelimiter()); } else { itValues = new SingletonIterator(value); } while (itNodes.hasNext() && itValues.hasNext()) { ((ConfigurationNode) itNodes.next()).setValue(itValues.next()); } // Add additional nodes if necessary while (itValues.hasNext()) { addPropertyDirect(key, itValues.next()); } // Remove remaining nodes while (itNodes.hasNext()) { clearNode((ConfigurationNode) itNodes.next()); } fireEvent(EVENT_SET_PROPERTY, key, value, false); } /** * Removes all values of the property with the given name and of keys that * start with this name. So if there is a property with the key * "foo" and a property with the key "foo.bar", a call * of <code>clearTree("foo")</code> would remove both properties. * * @param key the key of the property to be removed */ public void clearTree(String key) { fireEvent(EVENT_CLEAR_TREE, key, null, true); List nodes = fetchNodeList(key); for (Iterator it = nodes.iterator(); it.hasNext();) { removeNode((ConfigurationNode) it.next()); } fireEvent(EVENT_CLEAR_TREE, key, nodes, false); } /** * Removes the property with the given key. Properties with names that start * with the given key (i.e. properties below the specified key in the * hierarchy) won't be affected. * * @param key the key of the property to be removed */ public void clearProperty(String key) { fireEvent(EVENT_CLEAR_PROPERTY, key, null, true); List nodes = fetchNodeList(key); for (Iterator it = nodes.iterator(); it.hasNext();) { clearNode((ConfigurationNode) it.next()); } fireEvent(EVENT_CLEAR_PROPERTY, key, null, false); } /** * Returns an iterator with all keys defined in this configuration. * Note that the keys returned by this method will not contain any * indices. This means that some structure will be lost.</p> * * @return an iterator with the defined keys in this configuration */ public Iterator getKeys() { DefinedKeysVisitor visitor = new DefinedKeysVisitor(); getRootNode().visit(visitor); return visitor.getKeyList().iterator(); } /** * Returns an iterator with all keys defined in this configuration that * start with the given prefix. The returned keys will not contain any * indices. * * @param prefix the prefix of the keys to start with * @return an iterator with the found keys */ public Iterator getKeys(String prefix) { DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); if (containsKey(prefix)) { // explicitly add the prefix visitor.getKeyList().add(prefix); } List nodes = fetchNodeList(prefix); for (Iterator itNodes = nodes.iterator(); itNodes.hasNext();) { ConfigurationNode node = (ConfigurationNode) itNodes.next(); for (Iterator it = node.getChildren().iterator(); it.hasNext();) { ((ConfigurationNode) it.next()).visit(visitor); } for (Iterator it = node.getAttributes().iterator(); it.hasNext();) { ((ConfigurationNode) it.next()).visit(visitor); } } return visitor.getKeyList().iterator(); } /** * Returns the maximum defined index for the given key. This is useful if * there are multiple values for this key. They can then be addressed * separately by specifying indices from 0 to the return value of this * method. * * @param key the key to be checked * @return the maximum defined index for this key */ public int getMaxIndex(String key) { return fetchNodeList(key).size() - 1; } /** * Creates a copy of this object. This new configuration object will contain * copies of all nodes in the same structure. Registered event listeners * won't be cloned; so they are not registered at the returned copy. * * @return the copy * @since 1.2 */ public Object clone() { try { HierarchicalConfiguration copy = (HierarchicalConfiguration) super .clone(); // clone the nodes, too CloneVisitor v = new CloneVisitor(); getRootNode().visit(v); copy.setRootNode(v.getClone()); return copy; } catch (CloneNotSupportedException cex) { // should not happen throw new ConfigurationRuntimeException(cex); } } /** * Returns a configuration with the same content as this configuration, but * with all variables replaced by their actual values. This implementation * is specific for hierarchical configurations. It clones the current * configuration and runs a specialized visitor on the clone, which performs * interpolation on the single configuration nodes. * * @return a configuration with all variables interpolated * @since 1.5 */ public Configuration interpolatedConfiguration() { HierarchicalConfiguration c = (HierarchicalConfiguration) clone(); c.getRootNode().visit(new ConfigurationNodeVisitorAdapter() { public void visitAfterChildren(ConfigurationNode node) { node.setValue(interpolate(node.getValue())); } }); return c; } /** * Helper method for fetching a list of all nodes that are addressed by the * specified key. * * @param key the key * @return a list with all affected nodes (never <b>null </b>) */ protected List fetchNodeList(String key) { return getExpressionEngine().query(getRootNode(), key); } /** * Recursive helper method for fetching a property. This method processes * all facets of a configuration key, traverses the tree of properties and * fetches the the nodes of all matching properties. * * @param keyPart the configuration key iterator * @param node the actual node * @param nodes here the found nodes are stored * @deprecated Property keys are now evaluated by the expression engine * associated with the configuration; this method will no longer be called. * If you want to modify the way properties are looked up, consider * implementing you own <code>ExpressionEngine</code> implementation. */ protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart, Node node, Collection nodes) { } /** * Checks if the specified node is defined. * * @param node the node to be checked * @return a flag if this node is defined * @deprecated Use the method <code>{@link #nodeDefined(ConfigurationNode)}</code> * instead. */ protected boolean nodeDefined(Node node) { return nodeDefined((ConfigurationNode) node); } /** * Checks if the specified node is defined. * * @param node the node to be checked * @return a flag if this node is defined */ protected boolean nodeDefined(ConfigurationNode node) { DefinedVisitor visitor = new DefinedVisitor(); node.visit(visitor); return visitor.isDefined(); } /** * Removes the specified node from this configuration. This method ensures * that parent nodes that become undefined by this operation are also * removed. * * @param node the node to be removed * @deprecated Use the method <code>{@link #removeNode(ConfigurationNode)}</code> * instead. */ protected void removeNode(Node node) { removeNode((ConfigurationNode) node); } /** * Removes the specified node from this configuration. This method ensures * that parent nodes that become undefined by this operation are also * removed. * * @param node the node to be removed */ protected void removeNode(ConfigurationNode node) { ConfigurationNode parent = node.getParentNode(); if (parent != null) { parent.removeChild(node); if (!nodeDefined(parent)) { removeNode(parent); } } } /** * Clears the value of the specified node. If the node becomes undefined by * this operation, it is removed from the hierarchy. * * @param node the node to be cleared * @deprecated Use the method <code>{@link #clearNode(ConfigurationNode)}</code> * instead */ protected void clearNode(Node node) { clearNode((ConfigurationNode) node); } /** * Clears the value of the specified node. If the node becomes undefined by * this operation, it is removed from the hierarchy. * * @param node the node to be cleared */ protected void clearNode(ConfigurationNode node) { node.setValue(null); if (!nodeDefined(node)) { removeNode(node); } } /** * Returns a reference to the parent node of an add operation. Nodes for new * properties can be added as children of this node. If the path for the * specified key does not exist so far, it is created now. * * @param keyIt the iterator for the key of the new property * @param startNode the node to start the search with * @return the parent node for the add operation * @deprecated Adding new properties is now to a major part delegated to the * <code>ExpressionEngine</code> associated with this configuration instance. * This method will no longer be called. Developers who want to modify the * process of adding new properties should consider implementing their own * expression engine. */ protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode) { return null; } /** * Finds the last existing node for an add operation. This method traverses * the configuration tree along the specified key. The last existing node on * this path is returned. * * @param keyIt the key iterator * @param node the actual node * @return the last existing node on the given path * @deprecated Adding new properties is now to a major part delegated to the * <code>ExpressionEngine</code> associated with this configuration instance. * This method will no longer be called. Developers who want to modify the * process of adding new properties should consider implementing their own * expression engine. */ protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node) { return null; } /** * Creates the missing nodes for adding a new property. This method ensures * that there are corresponding nodes for all components of the specified * configuration key. * * @param keyIt the key iterator * @param root the base node of the path to be created * @return the last node of the path * @deprecated Adding new properties is now to a major part delegated to the * <code>ExpressionEngine</code> associated with this configuration instance. * This method will no longer be called. Developers who want to modify the * process of adding new properties should consider implementing their own * expression engine. */ protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root) { return null; } /** * Creates a new <code>Node</code> object with the specified name. This * method can be overloaded in derived classes if a specific node type is * needed. This base implementation always returns a new object of the * <code>Node</code> class. * * @param name the name of the new node * @return the new node */ protected Node createNode(String name) { return new Node(name); } /** * Helper method for processing a node add data object obtained from the * expression engine. This method will create all new nodes. * * @param data the data object * @return the new node * @since 1.3 */ private ConfigurationNode processNodeAddData(NodeAddData data) { ConfigurationNode node = data.getParent(); // Create missing nodes on the path for (Iterator it = data.getPathNodes().iterator(); it.hasNext();) { ConfigurationNode child = createNode((String) it.next()); node.addChild(child); node = child; } // Add new target node ConfigurationNode child = createNode(data.getNewNodeName()); if (data.isAttribute()) { node.addAttribute(child); } else { node.addChild(child); } return child; } /** * Clears all reference fields in a node structure. A configuration node can * store a so-called "reference". The meaning of this data is * determined by a concrete sub class. Typically such references are * specific for a configuration instance. If this instance is cloned or * copied, they must be cleared. This can be done using this method. * * @param node the root node of the node hierarchy, in which the references * are to be cleared * @since 1.4 */ protected static void clearReferences(ConfigurationNode node) { node.visit(new ConfigurationNodeVisitorAdapter() { public void visitBeforeChildren(ConfigurationNode node) { node.setReference(null); } }); } /** * A data class for storing (hierarchical) property information. A property * can have a value and an arbitrary number of child properties. From * version 1.3 on this class is only a thin wrapper over the * <code>{@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode}</code> * class that exists mainly for the purpose of backwards compatibility. */ public static class Node extends DefaultConfigurationNode implements Serializable { /** * The serial version UID. */ private static final long serialVersionUID = -6357500633536941775L; /** * Creates a new instance of <code>Node</code>. */ public Node() { super(); } /** * Creates a new instance of <code>Node</code> and sets the name. * * @param name the node's name */ public Node(String name) { super(name); } /** * Creates a new instance of <code>Node</code> and sets the name and the value. * * @param name the node's name * @param value the value */ public Node(String name, Object value) { super(name, value); } /** * Creates a new instance of <code>Node</code> based on the given * source node. All properties of the source node, including its * children and attributes, will be copied. * * @param src the node to be copied */ public Node(ConfigurationNode src) { this(src.getName(), src.getValue()); setReference(src.getReference()); for (Iterator it = src.getChildren().iterator(); it.hasNext();) { ConfigurationNode nd = (ConfigurationNode) it.next(); // Don't change the parent node ConfigurationNode parent = nd.getParentNode(); addChild(nd); nd.setParentNode(parent); } for (Iterator it = src.getAttributes().iterator(); it.hasNext();) { ConfigurationNode nd = (ConfigurationNode) it.next(); // Don't change the parent node ConfigurationNode parent = nd.getParentNode(); addAttribute(nd); nd.setParentNode(parent); } } /** * Returns the parent of this node. * * @return this node's parent (can be <b>null</b>) */ public Node getParent() { return (Node) getParentNode(); } /** * Sets the parent of this node. * * @param node the parent node */ public void setParent(Node node) { setParentNode(node); } /** * Adds the given node to the children of this node. * * @param node the child to be added */ public void addChild(Node node) { addChild((ConfigurationNode) node); } /** * Returns a flag whether this node has child elements. * * @return <b>true</b> if there is a child node, <b>false</b> otherwise */ public boolean hasChildren() { return getChildrenCount() > 0 || getAttributeCount() > 0; } /** * Removes the specified child from this node. * * @param child the child node to be removed * @return a flag if the child could be found */ public boolean remove(Node child) { return child.isAttribute() ? removeAttribute(child) : removeChild(child); } /** * Removes all children with the given name. * * @param name the name of the children to be removed * @return a flag if children with this name existed */ public boolean remove(String name) { boolean childrenRemoved = removeChild(name); boolean attrsRemoved = removeAttribute(name); return childrenRemoved || attrsRemoved; } /** * A generic method for traversing this node and all of its children. * This method sends the passed in visitor to this node and all of its * children. * * @param visitor the visitor * @param key here a configuration key with the name of the root node of * the iteration can be passed; if this key is not <b>null </b>, the * full pathes to the visited nodes are builded and passed to the * visitor's <code>visit()</code> methods */ public void visit(NodeVisitor visitor, ConfigurationKey key) { int length = 0; if (key != null) { length = key.length(); if (getName() != null) { key .append(StringUtils .replace( isAttribute() ? ConfigurationKey .constructAttributeKey(getName()) : getName(), String .valueOf(ConfigurationKey.PROPERTY_DELIMITER), ConfigurationKey.ESCAPED_DELIMITER)); } } visitor.visitBeforeChildren(this, key); for (Iterator it = getChildren().iterator(); it.hasNext() && !visitor.terminate();) { ((Node) it.next()).visit(visitor, key); } for (Iterator it = getAttributes().iterator(); it.hasNext() && !visitor.terminate();) { ((Node) it.next()).visit(visitor, key); } if (key != null) { key.setLength(length); } visitor.visitAfterChildren(this, key); } } /** * <p>Definition of a visitor class for traversing a node and all of its * children.</p><p>This class defines the interface of a visitor for * <code>Node</code> objects and provides a default implementation. The * method <code>visit()</code> of <code>Node</code> implements a generic * iteration algorithm based on the <em>Visitor</em> pattern. By providing * different implementations of visitors it is possible to collect different * data during the iteration process.</p> * */ public static class NodeVisitor { /** * Visits the specified node. This method is called during iteration for * each node before its children have been visited. * * @param node the actual node * @param key the key of this node (may be <b>null </b>) */ public void visitBeforeChildren(Node node, ConfigurationKey key) { } /** * Visits the specified node after its children have been processed. * This gives a visitor the opportunity of collecting additional data * after the child nodes have been visited. * * @param node the node to be visited * @param key the key of this node (may be <b>null </b>) */ public void visitAfterChildren(Node node, ConfigurationKey key) { } /** * Returns a flag that indicates if iteration should be stopped. This * method is called after each visited node. It can be useful for * visitors that search a specific node. If this node is found, the * whole process can be stopped. This base implementation always returns * <b>false </b>. * * @return a flag if iteration should be stopped */ public boolean terminate() { return false; } } /** * A specialized visitor that checks if a node is defined. * "Defined" in this terms means that the node or at least one of * its sub nodes is associated with a value. * */ static class DefinedVisitor extends ConfigurationNodeVisitorAdapter { /** Stores the defined flag. */ private boolean defined; /** * Checks if iteration should be stopped. This can be done if the first * defined node is found. * * @return a flag if iteration should be stopped */ public boolean terminate() { return isDefined(); } /** * Visits the node. Checks if a value is defined. * * @param node the actual node */ public void visitBeforeChildren(ConfigurationNode node) { defined = node.getValue() != null; } /** * Returns the defined flag. * * @return the defined flag */ public boolean isDefined() { return defined; } } /** * A specialized visitor that fills a list with keys that are defined in a * node hierarchy. */ class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter { /** Stores the list to be filled. */ private Set keyList; /** A stack with the keys of the already processed nodes. */ private Stack parentKeys; /** * Default constructor. */ public DefinedKeysVisitor() { keyList = new ListOrderedSet(); parentKeys = new Stack(); } /** * Creates a new <code>DefinedKeysVisitor</code> instance and sets the * prefix for the keys to fetch. * * @param prefix the prefix */ public DefinedKeysVisitor(String prefix) { this(); parentKeys.push(prefix); } /** * Returns the list with all defined keys. * * @return the list with the defined keys */ public Set getKeyList() { return keyList; } /** * Visits the node after its children has been processed. Removes this * node's key from the stack. * * @param node the node */ public void visitAfterChildren(ConfigurationNode node) { parentKeys.pop(); } /** * Visits the specified node. If this node has a value, its key is added * to the internal list. * * @param node the node to be visited */ public void visitBeforeChildren(ConfigurationNode node) { String parentKey = parentKeys.isEmpty() ? null : (String) parentKeys.peek(); String key = getExpressionEngine().nodeKey(node, parentKey); parentKeys.push(key); if (node.getValue() != null) { keyList.add(key); } } } /** * A specialized visitor that is able to create a deep copy of a node * hierarchy. */ static class CloneVisitor extends ConfigurationNodeVisitorAdapter { /** A stack with the actual object to be copied. */ private Stack copyStack; /** Stores the result of the clone process. */ private ConfigurationNode result; /** * Creates a new instance of <code>CloneVisitor</code>. */ public CloneVisitor() { copyStack = new Stack(); } /** * Visits the specified node after its children have been processed. * * @param node the node */ public void visitAfterChildren(ConfigurationNode node) { ConfigurationNode copy = (ConfigurationNode) copyStack.pop(); if (copyStack.isEmpty()) { result = copy; } } /** * Visits and copies the specified node. * * @param node the node */ public void visitBeforeChildren(ConfigurationNode node) { ConfigurationNode copy = (ConfigurationNode) node.clone(); copy.setParentNode(null); if (!copyStack.isEmpty()) { if (node.isAttribute()) { ((ConfigurationNode) copyStack.peek()).addAttribute(copy); } else { ((ConfigurationNode) copyStack.peek()).addChild(copy); } } copyStack.push(copy); } /** * Returns the result of the clone process. This is the root node of the * cloned node hierarchy. * * @return the cloned root node */ public ConfigurationNode getClone() { return result; } } /** * A specialized visitor base class that can be used for storing the tree of * configuration nodes. The basic idea is that each node can be associated * with a reference object. This reference object has a concrete meaning in * a derived class, e.g. an entry in a JNDI context or an XML element. When * the configuration tree is set up, the <code>load()</code> method is * responsible for setting the reference objects. When the configuration * tree is later modified, new nodes do not have a defined reference object. * This visitor class processes all nodes and finds the ones without a * defined reference object. For those nodes the <code>insert()</code> * method is called, which must be defined in concrete sub classes. This * method can perform all steps to integrate the new node into the original * structure. * */ protected abstract static class BuilderVisitor extends NodeVisitor { /** * Visits the specified node before its children have been traversed. * * @param node the node to visit * @param key the current key */ public void visitBeforeChildren(Node node, ConfigurationKey key) { Collection subNodes = new LinkedList(node.getChildren()); subNodes.addAll(node.getAttributes()); Iterator children = subNodes.iterator(); Node sibling1 = null; Node nd = null; while (children.hasNext()) { // find the next new node do { sibling1 = nd; nd = (Node) children.next(); } while (nd.getReference() != null && children.hasNext()); if (nd.getReference() == null) { // find all following new nodes List newNodes = new LinkedList(); newNodes.add(nd); while (children.hasNext()) { nd = (Node) children.next(); if (nd.getReference() == null) { newNodes.add(nd); } else { break; } } // Insert all new nodes Node sibling2 = (nd.getReference() == null) ? null : nd; for (Iterator it = newNodes.iterator(); it.hasNext();) { Node insertNode = (Node) it.next(); if (insertNode.getReference() == null) { Object ref = insert(insertNode, node, sibling1, sibling2); if (ref != null) { insertNode.setReference(ref); } sibling1 = insertNode; } } } } } /** * Inserts a new node into the structure constructed by this builder. * This method is called for each node that has been added to the * configuration tree after the configuration has been loaded from its * source. These new nodes have to be inserted into the original * structure. The passed in nodes define the position of the node to be * inserted: its parent and the siblings between to insert. The return * value is interpreted as the new reference of the affected * <code>Node</code> object; if it is not <b>null </b>, it is passed * to the node's <code>setReference()</code> method. * * @param newNode the node to be inserted * @param parent the parent node * @param sibling1 the sibling after which the node is to be inserted; * can be <b>null </b> if the new node is going to be the first child * node * @param sibling2 the sibling before which the node is to be inserted; * can be <b>null </b> if the new node is going to be the last child * node * @return the reference object for the node to be inserted */ protected abstract Object insert(Node newNode, Node parent, Node sibling1, Node sibling2); } }